/*****************************************************************************
 *   Copyright 2011 ZHUANG Yuan              	                             *
 *   SudokuITS as Final Year Project for BScCS, City University of Hong Kong *
 *                                      									 *
 *   Licensed under the Apache License, Version 2.0 (the "License");         *
 *   you may not use this file except in compliance with the License.        *
 *   You may obtain a copy of the License at                                 *
 *                                                                           *
 *   http://www.apache.org/licenses/LICENSE-2.0                              *
 *                                                                           *
 *   Unless required by applicable law or agreed to in writing, software     *
 *   distributed under the License is distributed on an "AS IS" BASIS,       *
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*
 *   See the License for the specific language governing permissions and     *
 *   limitations under the License.                                          *
 *****************************************************************************/

package android.sudoku.its.util;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.sudoku.its.expert.Expert;
import android.sudoku.its.expert.StrategiesUtil;
import android.sudoku.its.tutor.StrategyExplainer;
import android.sudoku.its.tutor.Tutor;
import android.sudoku.its.views.R;
import android.util.Log;

public class GamePageUtil {	
	/**
	 * Randomly choose a puzzle from the xml resource file
	 * @param diff		Level of difficulty
	 * @return			A puzzle string of the chosen difficulty
	 */
	public String chooseRandomPuzzle(Context context, int diff) {
		String[] puzzles = null;
		
		switch (diff) {
		case GameVariables.DIFF_LEVEL_BEGINNER:
			puzzles = context.getResources().getStringArray(R.array.easy_puzzles);
			break;
		case GameVariables.DIFF_LEVEL_INTERMEDIATE:
			puzzles = context.getResources().getStringArray(R.array.normal_puzzles);
			break;
		case GameVariables.DIFF_LEVEL_ADVANCED:
			puzzles = context.getResources().getStringArray(R.array.hard_puzzles);
			break;
		}
		
		// Generate random number within range 0 ~ the number of puzzles
		int rangeMin = 0, rangeMax = puzzles.length - 1;
		int randomIndex = rangeMin + (int)(Math.random() * ((rangeMax - rangeMin) + 1));
		
		return puzzles[randomIndex];
	}
	
	/**
	 * Verify if an input number is valid
	 * @param clues		an array containing all clues
	 * @param num		input number
	 * @param nRow		row index of the cell that receives input
	 * @param nCol		column index of the cell that receives input
	 * @return			a boolean value indicating if the input is valid
	 */
	public boolean isInputValid(int[] solution, int num, int nRow, int nCol) {
		Boolean isValid = false;
		
		int index = nRow * GameVariables.HOUSE_SIZE + nCol;
		if (num == solution[index])
			return isValid = true;
		
		return isValid;
	}

	/**
	 * Convert a one-dimension clue array to two-dimension 
	 * @param arr	one-dimension clue array
	 * @return		two-dimension clue array
	 */
	private int[][] convertClueArray(int[] arr) {
		int[][] clues = new int[GameVariables.HOUSE_SIZE][GameVariables.HOUSE_SIZE];
		
		for (int row=0; row<GameVariables.HOUSE_SIZE; row++)
			for (int col=0; col<GameVariables.HOUSE_SIZE; col++)
				clues[row][col] = arr[row * GameVariables.HOUSE_SIZE + col];
		
		return clues;
	}
	
	public boolean isPencilmarkValid(int[][] pencilmarks, int num, int cellIndex, int pencilmarkIndex) {
		Boolean isValid = false;

		if (pencilmarks[cellIndex][pencilmarkIndex] != num) 
			isValid = true;
		
		return isValid;
	}
	
	public int[][] getTwoDClues(int[] clues) {
		return convertClueArray(clues);
	}
	
	/**
	 * Convert clues from an integer array to a string
	 * @return		a string containing clues
	 */
	public String convertClueFromIntToString(int[] clues) {
		StringBuilder sb = new StringBuilder();
		
		for (int element : clues)
			sb.append(element);
		
		return sb.toString();
	}	
	
	private int[] generatePuzzleSolutionFunc(Context context, int[] clues)  {		
		Expert expert = new Expert(getTwoDClues(clues));		// initialize expert model		
		List<List<Integer>> cm = new ArrayList<List<Integer>>();
		List<List<Integer>> asm = new ArrayList<List<Integer>>();	
		StrategiesUtil strategiesUtil = new StrategiesUtil();
		StrategyExplainer strategyExplainer = new StrategyExplainer();
		
		for (int rowIndex = 0; rowIndex < GameVariables.HOUSE_SIZE; rowIndex ++) 
			for (int colIndex = 0; colIndex < GameVariables.HOUSE_SIZE; colIndex ++) {
				int cellIndex = rowIndex * GameVariables.HOUSE_SIZE + colIndex;	
				asm = expert.getApplicableStrategyMatrix();
				if (clues[cellIndex] == 0) { // if this is a unsolved cell
					List<Integer> asmForCell = asm.get(cellIndex);
					if (!asmForCell.isEmpty()) { // if there is applicable strategy in this cell
						int strategyIndex = asmForCell.get(0); // apply the first strategy of this cell											
						cm = expert.getCandidateMatrix();
						switch (strategyIndex) {							
							case GameVariables.FULL_HOUSE_INDEX:	
							case GameVariables.NAKED_SINGLE_INDEX:
							case GameVariables.HIDDEN_SINGLE_INDEX:
								int answer = 0;
								switch (strategyIndex) {
									case GameVariables.FULL_HOUSE_INDEX:	
									case GameVariables.NAKED_SINGLE_INDEX:
										answer = cm.get(cellIndex).get(0);
										break;
									case GameVariables.HIDDEN_SINGLE_INDEX:
										// Make a copy of the target cell from Candidate Matrix
										List<Integer> candidateOfTargetCell = cm.get(cellIndex);
										int candidate = 0;
										while (candidate == 0) {
											// Check row
											candidate = strategiesUtil.isHiddenSingleApplicableFunc(cm, candidateOfTargetCell, GameVariables.ROW, rowIndex);
											if (candidate != 0) {
												answer = candidate;
												break;
											}
											candidate = strategiesUtil.isHiddenSingleApplicableFunc(cm, candidateOfTargetCell, GameVariables.COLUMN, colIndex);
											if (candidate != 0) {
												answer = candidate;
												break;
											}
										    int block = rowIndex/GameVariables.BLOCK_SIDE_LENGTH * GameVariables.BLOCK_SIDE_LENGTH + colIndex/GameVariables.BLOCK_SIDE_LENGTH;
											candidate = strategiesUtil.isHiddenSingleApplicableFunc(cm, candidateOfTargetCell, GameVariables.BLOCK, block);
											if (candidate != 0) {
												answer = candidate;
												break;
											}
										}
										break;
								}														
								// Update clue
								clues[cellIndex] = answer;
								// Update the expert model including the Candidate Matrix and the Applicable Strategy Matrix
								expert.updateWorkingMemory(answer, getTwoDClues(clues));
								break;							
							case GameVariables.NAKED_PAIR_INDEX:
								expert = checkNakedPair(expert, cellIndex, rowIndex, colIndex, clues);
								break;								
							case GameVariables.HIDDEN_PAIR_INDEX:
								Tutor tutor = new Tutor();
								strategyExplainer.giveHiddenPairResult(expert, rowIndex, colIndex, cm, getTwoDClues(clues), tutor);
								break;	
													
						}										
					}
				}								
			}
		return clues;
	}
	
	private Expert checkNakedPair(Expert expert, int cellIndex, int rowIndex, int colIndex, int[] clues) {
		List<List<Integer>> cm = expert.getCandidateMatrix();
		int[] cellPairIndexes = {0, 0};
		cellPairIndexes[0] = cellIndex;
		int[] candidatePairIntArr = {0, 0};
		for (int i = 0; i < 2; i ++)
			candidatePairIntArr[i] = cm.get(cellIndex).get(i);
		
		/** Check the row containing the target cell **/
		for (int col = 0; col < GameVariables.HOUSE_SIZE; col++) {
			int index = rowIndex * GameVariables.HOUSE_SIZE + col;
			// Try to find another cell in this row that can apply Naked Pair except the target cell
			if ((index != cellIndex) && (cm.get(cellIndex).equals(cm.get(index)))) {				
				cellPairIndexes[1] = index;
				expert.updateExpertForNakedPair(candidatePairIntArr, cellPairIndexes, getTwoDClues(clues));										
				}
			}
		
		/** Check the column containing the target cell **/
		for (int row = 0; row < GameVariables.HOUSE_SIZE; row++)  {			
			int index = row * GameVariables.HOUSE_SIZE + colIndex;
			// Try to find another cell in this column that can apply Naked Pair except the target cell
			if ((index != cellIndex) && (cm.get(cellIndex).equals(cm.get(index)))) {
				cellPairIndexes[1] = index;				
				expert.updateExpertForNakedPair(candidatePairIntArr, cellPairIndexes, getTwoDClues(clues));										
			}
		}
		
		/** Check the block containing the target cell **/
		int blockStartRowIndex = (rowIndex/GameVariables.BLOCK_SIDE_LENGTH)*GameVariables.BLOCK_SIDE_LENGTH;
		int blockStartColIndex = (colIndex/GameVariables.BLOCK_SIDE_LENGTH)*GameVariables.BLOCK_SIDE_LENGTH;
		for (int row = 0; row < GameVariables.BLOCK_SIDE_LENGTH; row++) {
			for (int col = 0; col < GameVariables.BLOCK_SIDE_LENGTH; col++) {
				int index = (row + blockStartRowIndex) * GameVariables.HOUSE_SIZE + (col + blockStartColIndex);
				if ((index != cellIndex) && (cm.get(cellIndex).equals(cm.get(index)))) {
					cellPairIndexes[1] = index;
					expert.updateExpertForNakedPair(candidatePairIntArr, cellPairIndexes, getTwoDClues(clues));											
				}
			}
		}
		return expert;
	}
	
	public int[] generatePuzzleSolution(Context context, int[] clues) {			
		int[] solution = clues.clone();
		// Check if all cells have been solved
		for (int i = 0; i < GameVariables.PUZZLE_LENGTH; i ++) {			
			if (solution[i] == 0) {
				solution = generatePuzzleSolutionFunc(context, solution);
				int unsolvedCount = 0;
				for (int j = 0; j < GameVariables.PUZZLE_LENGTH; j ++)
					if (solution[j] == 0)
						unsolvedCount ++;				
				Log.d("unsolved", Integer.toString(unsolvedCount));
				i = 0;
			}
		}
						
		return solution;
	}
	
	/**
	 * Convert a puzzle string to an integer array
	 * @param puzString		a puzzle string
	 * @return				a puzzle integer array 
	 */
	public int[] convertClueFromStringToIntArray(String puzString){
		int[] puz = new int[GameVariables.PUZZLE_LENGTH];
		for (int i=0; i<GameVariables.PUZZLE_LENGTH; i++)
			puz[i] = puzString.charAt(i) - '0';
		return puz;
	}		

	/**
	 * Convert pencil marks from a 2d integer array to a string
	 * @return		a string containing pencil marks
	 */
	public String convertPencilmarkFromIntToString(int[][] pencilmarks) {
		StringBuilder sb = new StringBuilder();
				
		for (int i=0; i<GameVariables.PUZZLE_LENGTH; i++) 
			for (int element : pencilmarks[i])
				sb.append(element);
		
		return sb.toString();
	}
	
	/**
	 * Convert a pencil mark string to a 2d integer array
	 * @param pmStr		a pencil mark string
	 * @return			a pencil mark 2d integer array
	 */
	public int[][] convertPencilmarkFromStringToIntArray(String pmStr) {
		int[][] pm = new int[GameVariables.PUZZLE_LENGTH][GameVariables.HOUSE_SIZE];
		for (int i=0; i<GameVariables.PUZZLE_LENGTH; i++)
			for (int j=0; j<GameVariables.HOUSE_SIZE; j++)
				pm[i][j] = pmStr.charAt(i * GameVariables.HOUSE_SIZE + j) - '0';				
		return pm;
	}
	
	/**
	 * Get auto-instruction time interval according to puzzle difficulty level
	 * @param puzzleDiff		Puzzle difficulty level
	 * @return					A time interval 
	 */
	public long getAutoInstrTimeIntervalByPuzzleDiff(int puzzleDiff) {
		long defaultInterval = GameVariables.AUTO_INSTR_INTERVAL_DIFF_BEGINNER;
		
		if (puzzleDiff == GameVariables.DIFF_LEVEL_BEGINNER)
			return GameVariables.AUTO_INSTR_INTERVAL_DIFF_BEGINNER;
		else if (puzzleDiff == GameVariables.DIFF_LEVEL_INTERMEDIATE)
			return GameVariables.AUTO_INSTR_INTERVAL_DIFF_INTERMEDIATE;
		else if (puzzleDiff == GameVariables.DIFF_LEVEL_ADVANCED)
			return GameVariables.AUTO_INSTR_INTERVAL_DIFF_ADVANCED;
		else
			return defaultInterval;
	}
	
	/**
	 * Get auto-instruction time interval according to student level
	 * @param puzzleDiff		Student level
	 * @return					A time interval 
	 */
	public long getAutoInstrTimeIntervalByUserLevel(int studentLevel) {
		long defaultInterval = GameVariables.AUTO_INSTR_INTERVAL_USER_BEGINNER;
		
		if (studentLevel == GameVariables.LEVEL_BEGINNER)
			return GameVariables.AUTO_INSTR_INTERVAL_USER_BEGINNER;
		else if (studentLevel == GameVariables.LEVEL_INTERMEDIATE)
			return GameVariables.AUTO_INSTR_INTERVAL_USER_INTERMEDIATE;
		else if (studentLevel == GameVariables.LEVEL_ADVANCED)
			return GameVariables.AUTO_INSTR_INTERVAL_USER_ADVANCED;
		else
			return defaultInterval;
	}
	
	/**
	 * Mechanism to determine auto-instruction time interval 
	 * @param puzzleDiff		Puzzle difficulty level
	 * @param studentLevel		Student level
	 * @return
	 */
	public long getAutoInstrTimeInterval(int puzzleDiff, int studentLevel) {		
		return getAutoInstrTimeIntervalByPuzzleDiff(puzzleDiff) 
				+ getAutoInstrTimeIntervalByUserLevel(studentLevel);
	}
	
	/**
	 * Check if it is the end of the game by checking if the user has solved all cells
	 * @param clues		An array containing all clues
	 * @return			A boolean value indicating if it the end of the game
	 */
	public boolean ifEndOfGame(int[] clues) {
		boolean ifEndOfGame = false;
		
		for (int i = 0; i < GameVariables.PUZZLE_LENGTH; i ++)
			if (clues[i] == 0) // indicates this cell is not solved yet
				return ifEndOfGame;
		
		ifEndOfGame = true;
		return ifEndOfGame;
	}
}
